package ttk.muxiuesd.world.chunk;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Disposable;
import ttk.muxiuesd.block.Block;
import ttk.muxiuesd.entity.Player;
import ttk.muxiuesd.gameinterface.Drawable;
import ttk.muxiuesd.gameinterface.ShapeRenderable;
import ttk.muxiuesd.gameinterface.Updateable;
import ttk.muxiuesd.util.*;
import ttk.muxiuesd.world.MainWorld;

import java.util.ArrayList;
import java.util.Objects;

/**
 * 世界区块的管理器，负责区块的加载和卸载
 * */
public final class ChunksManager implements Updateable, Drawable, ShapeRenderable, Disposable {
    public static String TAG;

    public boolean chunkEdgeRender = false;
    public boolean wallHitboxRender = false;

    private MainWorld world;
    private final Player player;
    private Vector2 playerLastPosition;
    private WorldMapNoise noise;

    public static final float Slope = 100.0f;   //地形坡度，生成地形时的参数
    private float visualRange = 64f;

    //当前活跃的线程
    private ArrayList<Chunk> activeChunks;
    //加载和卸载区块的延迟队列
    private ArrayList<Chunk> _loadChunks   = new ArrayList<>();
    private ArrayList<Chunk> _unloadChunks = new ArrayList<>();


    public ChunksManager(MainWorld world){
        this.noise = new WorldMapNoise(world.worldSeed);
        this.player = world.getEntityGroup().player;
        this.playerLastPosition = new Vector2(this.player.x + 1000, this.player.y + 1000);
        this.activeChunks = new ArrayList<>();
        //预加载一次
        this.update(0);
    }

    /**
     * 计算需要被加载的区块
     * TODO 实现更好的加载算法，多线程处理区块加载
     * */
    private void calculateNeedLoadedChunk () {
        //计算玩家所在区块坐标编号
        ChunkPosition chunkPosition = this.getPlayerChunkPosition(this.player);
        int playerChunkX = chunkPosition.getX();
        int playerChunkY = chunkPosition.getY();

        //System.out.println("("+ player.x+ "," + player.y +")" + "("+ playerChunkX + "," + playerChunkY +")");
        //TODO 实现更好的循环
        for (int y = -3; y < 3; y++) {
            for (int x = -3; x < 3; x++) {
                int newChunkX = playerChunkX + x;
                int newChunkY = playerChunkY + y;

                float distance = Util.getDistance(
                    this.player.x + this.player.width / 2,
                    this.player.y + this.player.height / 2,
                    newChunkX * Chunk.ChunkWidth + Chunk.ChunkWidth / 2f,
                    newChunkY * Chunk.ChunkHeight + Chunk.ChunkHeight / 2f);

                if (distance <= this.visualRange) {
                    this.loadChunk(newChunkX, newChunkY);
                }
            }
        }
    }

    /**
     * 计算需要被卸载的区块
     * */
    private void calculateNeedUnloadedChunk () {
        for (Chunk chunk: this.activeChunks) {
            float distance = Util.getDistance(this.player.x, this.player.y,
                chunk.getChunkPosition().getX() * Chunk.ChunkWidth + Chunk.ChunkWidth / 2f,
                chunk.getChunkPosition().getY() * Chunk.ChunkHeight + Chunk.ChunkHeight / 2f);
            if (distance > this.visualRange) {
                this.unloadChunk(chunk);
            }
        }
    }

    /**
     * 加载区块
     * */
    public void loadChunk (int chunkX, int chunkY) {
        boolean chunkExist = false;
        for (Chunk chunk: this._loadChunks) {
            ChunkPosition chunkPosition = chunk.getChunkPosition();
            if (chunkPosition.getX() == chunkX && chunkPosition.getY() == chunkY) {
                //Log.error(TAG, "编号为：(" + chunkX +","+ chunkY +")的区块已加载！！！");
                chunkExist = true;
                break;
            }
        }
        for (Chunk chunk: this.activeChunks) {
            ChunkPosition chunkPosition = chunk.getChunkPosition();
            if (chunkPosition.getX() == chunkX && chunkPosition.getY() == chunkY) {
                //Log.error(TAG, "编号为：(" + chunkX +","+ chunkY +")的区块已活跃！！！");
                chunkExist = true;
                break;
            }
        }
        if (chunkExist) {
            //TODO 实现如果区块已存在的更多操作，比如加载保存的此区块的数据
            return;
        }
        Chunk chunk = this.initChunk(chunkX, chunkY);
        this._loadChunks.add(chunk);
        //Log.print(TAG, "加载编号为：(" + chunkX +","+ chunkY +")的区块");
    }

    /**
     * 卸载区块
     * */
    public void unloadChunk (Chunk chunk) {
        boolean removed = false;
        //先在加载队列里查找
        for (Chunk loadChunk: this._loadChunks) {
            if (Objects.equals(chunk, loadChunk)) {
                this._loadChunks.remove(chunk);
                removed = true;
                //Log.print(TAG, "卸载编号为：(" + chunk.chunkX +","+ chunk.chunkY +")的区块");
                break;
            }
        }
        if (removed) {
            return;
        }
        this._unloadChunks.add(chunk);
        //Log.print(TAG, "卸载编号为：(" + chunk.chunkX +","+ chunk.chunkY +")的区块");
    }

    /**
     * 初始化区块
     * */
    private Chunk initChunk (int chunkX, int chunkY) {
        Chunk chunk = new Chunk(this);
        chunk.setChunkPosition(new ChunkPosition(chunkX, chunkY));
        chunk.initBlock();
        chunk.initWall();
        return chunk;
    }


    @Override
    public void draw(Batch batch) {
        batch.setColor(Color.WHITE);
        for (Chunk chunk: this.activeChunks) {
            chunk.draw(batch);
        }
        //batch.setColor(255, 255, 255, 255);
    }

    private float loadChunkTick = 0.2f;
    private float span = 0.2f;

    @Override
    public void update(float delta) {
        if (!this._loadChunks.isEmpty()) {
            this.activeChunks.addAll(this._loadChunks);
            this._loadChunks.clear();
        }
        if (!this._unloadChunks.isEmpty()) {
            this._unloadChunks.forEach(Chunk::dispose);
            this.activeChunks.removeAll(this._unloadChunks);
            //System.out.println(this.activeChunks.size());
            this._unloadChunks.clear();
        }

        for (Chunk chunk: this.activeChunks) {
            chunk.update(delta);
        }

        if (span >= loadChunkTick && this.playerMoved()) {
            this.calculateNeedLoadedChunk();
            this.calculateNeedUnloadedChunk();
            span = 0;
            this.playerLastPosition.set(this.player.x, this.player.y);
            //Log.print(TAG, "计算需要加载和卸载的区块");
        }else if (span <= loadChunkTick){
            //防止玩家停止太久导致span超出最大值
            span += delta;
        }
    }

    /*public static synchronized ChunksManager getInstance(Player player) {
        if (Objects.isNull(instance)) {
            instance = new ChunksManager(player);
            TAG = instance.getClass().getName();
        }
        return instance;
    }*/

    @Override
    public void dispose() {
        for (Chunk activeChunk: this.activeChunks) {
            activeChunk.dispose();
        }
    }

    @Override
    public void render(ShapeRenderer batch) {
        for (Chunk chunk : this.activeChunks) {
            chunk.render(batch);
        }
    }

    /**
     * 获取世界坐标上对应的方块
     * @return
     */
    public Block getBlock (float wx, float wy) {
        int nx = (int) Util.fastRound(wx);
        int ny = (int) Util.fastRound(wy);

        BlockPosition blockPosition = new BlockPosition(nx, ny);
        ChunkPosition chunkPosition = this.getChunkPosition(wx, wy);

        Chunk chunk = this.getChunk(chunkPosition);
        if (chunk == null) {
            Log.error(TAG, "获取的区块为null！！！");
            throw new RuntimeException(chunkPosition.toString() + "这个区块坐标对应的区块为null，可能是还未加载！！！");
        }
        Block block = chunk.seekBlock(blockPosition.getX(), blockPosition.getY());
        //如果在当前区块找不到的话
        if (block == null) {
            //在临近区块寻找
            for (int chunkY = -1; chunkY < 2; chunkY++) {
                for (int chunkX = -1; chunkX < 2; chunkX++) {
                    int cx = chunkPosition.getX() + chunkX;
                    int cy = chunkPosition.getY() + chunkY;
                    ChunkPosition chunkPosition1 = new ChunkPosition(cx, cy);
                    //跳过已寻找过的区块
                    if (chunkPosition1.equals(chunkPosition)) {
                        continue;
                    }

                    Chunk chunk1 = this.getChunk(chunkPosition1);
                    //有可能是还未加载的区块
                    if (chunk1 == null) {
                        //TODO 解决在还未加载的区块中查找方块
                        Log.print(TAG, chunkPosition1.toString() + "区块尚未加载！！！");
                        continue;
                    }
                    Block block1 = chunk1.seekBlock(blockPosition.getX(), blockPosition.getY());
                    if (block1 != null) {
                        return block1;
                    }
                }
            }

            Log.print(TAG, "尽力了");
        }
        //运行到这里应该就是查找到方块了
        return block;
    }

    public Chunk getChunk (int chunkX, int chunkY) {
        return this.getChunk(new ChunkPosition(chunkX, chunkY));
    }
    /**
     * 获取区块
     * */
    public Chunk getChunk (ChunkPosition chunkPosition) {
        for (Chunk chunk: this.activeChunks) {
            if (chunk.getChunkPosition().equals(chunkPosition)) {
                //如果正在活跃的区块里有这个区块则返回对应的区块
                return chunk;
            }
        }
        //如果没有这个区块,暂时就这么处理
        //TODO
        return null;
    }



    /**
     * 核心算法：
     * 获取玩家所在的区块编号
     * */
    private ChunkPosition getPlayerChunkPosition(Player player) {
        Position playerCenter = player.getPlayerCenter();
        return this.getChunkPosition(playerCenter.x, playerCenter.y);
    }

    /**
     * 获取世界坐标所对应的区块编号
     * @param wx
     * @param wy
     * @return
     */
    private ChunkPosition getChunkPosition (float wx, float wy) {
        ChunkPosition cp = new ChunkPosition();

        cp.setX((int)(Math.abs(Util.fastRound(wx)) / Chunk.ChunkWidth));
        cp.setY((int)(Math.abs(Util.fastRound(wy)) / Chunk.ChunkHeight));

        if (wx < 0) {
            cp.setX(-1 * cp.getX() - 1);
        }
        if (wy < 0) {
            cp.setY(-1 * cp.getY() - 1);
        }
        return cp;
    }

    /**
     * 对外开放的方法：获取玩家目前所在区块的编号（区块坐标）
     * @return
     */
    public ChunkPosition getPlayerChunkPosition () {
        return this.getPlayerChunkPosition(this.player);
    }

    //移动阈值
    private float moveValue = 3f;
    /**
     * 如果玩家一段时间内移动距离大于这个阈值就返回true，否则为false
     * */
    private boolean playerMoved () {
        Vector2 lastPosition = this.playerLastPosition;
        float distance = Util.getDistance(this.player, lastPosition.x, lastPosition.y);
        return distance >= moveValue;
    }

    public WorldMapNoise getNoise() {
        return noise;
    }
}
